#include "tomasulo.h"

enum STATION_STATUS {RS_EMPTY, RS_READY, RS_WAIT_QJ, RS_WAIT_QK, RS_WAIT_BOTH};

int inst_delays[14];

typedef struct {

	int inst_index;
	int v_j;
	int v_k;
	int * q_j;
	int * q_k;
	int delay_cycles;
	int cur_cycle;
	int status;

} reservationStation;

typedef struct {
	reservationStation * stations; 
	int max_size;
	int cur_size;
	int in_flight_index;

} reservationStations;


typedef struct {
	Command * cmd;
	int issue;
	int execute;
	int write_cdb;
	
} inFlightLine;

typedef struct {
	inFlightLine * in_flight_instructions;
	int size;
} InFlight;

void init_rs(reservationStations * rs, int size, int * index);

queue InstructionQueue;

reservationStations addsub_rs;
reservationStations muldiv_rs;
reservationStations load_q;
reservationStations store_q;

InFlight in_flight;

void init_rs(reservationStations * rs, int size, int * index) {

	int i;

	rs->max_size = size;
	rs->cur_size = 0;

	rs->stations = (reservationStation *) malloc (size * sizeof(reservationStation));

	rs->in_flight_index = *index;
	*index = *index +size;

	for (i = 0; i < size; i++) {
		rs->stations[i].status = RS_EMPTY;
	}

	
}

int init_tomasulo() {
	
	int i;
	int i_f_index = 0;

	init_queue(&InstructionQueue, config_values[INSTRUCTION_Q_DEPTH]);

	init_rs(&addsub_rs, config_values[ADDSUB_RS], &i_f_index);
	init_rs(&muldiv_rs, config_values[MULDIV_RS], &i_f_index);
	init_rs(&load_q, config_values[LOAD_Q_DEPTH], &i_f_index);
	init_rs(&store_q, config_values[STORE_Q_DEPTH], &i_f_index);

	in_flight.size = addsub_rs.max_size + muldiv_rs.max_size + load_q.max_size + store_q.max_size;
	in_flight.in_flight_instructions = (inFlightLine *) malloc (in_flight.size * sizeof(inFlightLine));

	for (i = 0; i < REGISTER_NUM; i++) {
		registers[i].value = 0;
		registers[i].has_value = 1;
		registers[i].q_i = NULL;
	}

	for (i = 0; i < 14 ; i++) {
		if (i == 0 || i == 1 || i == 4 || i == 5)
			inst_delays[i] = config_values[ADDSUB_DELAY];
		else if (i == 2 || i == 3)
				inst_delays[i] = config_values[MULDIV_DELAY];
		else	inst_delays[i] = -1;
	}

	return 0;

}


int executeCommand(int cmd_addr) {
	
	/* Uncomment for debugging trace only!
	Warning! Runtime increases exponential!*/

	/*
	printf("\n");
	printRegistersForTrace(stdout);
	printf("pc      %d >>> %s ",cmd_addr,commands[cmd_addr].instruction);
	printArgv(cmd_addr, stdout);
	printf("\n");
	*/

	return (instructionPtr[commands[cmd_addr].inst_num]) (&commands[cmd_addr]);
}


int tomasulo(char * cmd_error, FILE * trace) {

	int i, cmd_result, new_addr;
	int cycle_issued , cycle_execution_started, cycle_write_cdb, cycle_commit;

	for (i = 0; i < cmd_count; i++) {
		current_instruction = i;
		strcpy(jumpToLabel,"");
		
	//printf("%s : ",commands[i].command_str);
		printf("[%d] %s\n", i, commands[i].command_str);
		fetchInstructionFromCache(i);
		cmd_result = executeCommand(i);
		// total_time++; to be implemented in cache

		//decrementAllBusyCycles(1);

		if (cmd_result == -1) {
			sprintf(cmd_error,"Error: invalid command or arguments (%d:%s)",i+1,commands[i].instruction);
			return -1;
		}

		cmd_committed++;

		cycle_issued = total_time;
		cycle_execution_started = cycle_issued + 1;
		cycle_write_cdb = inst_delays[commands[i].inst_num] == -1 ? -1 : cycle_execution_started + inst_delays[commands[i].inst_num];
		cycle_commit = cycle_write_cdb;

		fprintf(trace,"%s\t%d %d %d %d\n",commands[i].command_str,cycle_issued,cycle_execution_started,cycle_write_cdb,cycle_commit);

		if (cmd_result == 1 && strcmp(jumpToLabel,"")) {
			if ((new_addr = findLabel(jumpToLabel)) == -1) continue; // i
			i = new_addr - 1;
		}
		
		if (isHalt) break;
	}

	return 0;
}


int freeTomasulo() {

	if (in_flight.in_flight_instructions != NULL)
		free(in_flight.in_flight_instructions);

	if (addsub_rs.stations != NULL)
		free(addsub_rs.stations);

	if (muldiv_rs.stations != NULL)
		free(muldiv_rs.stations);

	if (load_q.stations != NULL)
		free(load_q.stations);

	if (store_q.stations != NULL)
		free(store_q.stations);

	if (InstructionQueue.q != NULL)
		free(InstructionQueue.q);

	return 0;
}